package com.esfak47.mediafetecher.plugin.download;

import com.esfak47.common.logger.Logger;
import com.esfak47.common.logger.LoggerFactory;
import com.esfak47.mediafetcher.core.event.EventSender;
import com.esfak47.mediafetcher.core.utils.JobLogSender;
import com.esfak47.mediafetcher.core.utils.JobUtils;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import okhttp3.Call;
import okhttp3.Response;

import java.io.*;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author tony
 */
public class DownloadTask {

    private static final String TAG = "DownloadTask";
    private static Logger log = LoggerFactory.getLogger(DownloadTask.class);
    //线程数
    private final int THREAD_COUNT = 4;
    //进度
    private final int MSG_PROGRESS = 1;
    //完成下载
    private final int MSG_FINISH = 2;
    //暂停
    private final int MSG_PAUSE = 3;
    //取消
    private final int MSG_CANCEL = 4;

    private final int MSF_ERROR = 5;

    private final EventBus eventBus;
    private final JobLogSender jobLogSender;
    private FilePoint mPoint;
    private long mFileLength;
    private boolean isDownloading = false;
    //子线程取消数量
    private int childCancelCount;
    //子线程暂停数量
    private int childPauseCount;
    private AtomicInteger childFinishCount;
    private HttpUtil mHttpUtil;
    private long[] mProgress;
    private File[] mCacheFiles;
    //临时占位文件
    private File mTmpFile;
    //是否暂停
    private boolean pause;
    //是否取消下载
    private boolean cancel;
    //下载回调监听
    private DownloadListener mListner;

    /**
     * 任务管理器初始化数据
     *
     * @param point
     * @param l
     * @param id
     */
    public DownloadTask(FilePoint point, DownloadListener l, EventSender eventSender, String id) {
        this.mPoint = point;
        this.mListner = l;
        this.mProgress = new long[THREAD_COUNT];
        this.mCacheFiles = new File[THREAD_COUNT];
        this.mHttpUtil = HttpUtil.getInstance();
        eventBus = new EventBus("downloadThread");
        eventBus.register(this);
        this.jobLogSender = JobUtils.createJobLogSender(id, eventSender);
    }

    /**
     * 任务回调消息
     *
     * @param msg
     */
    @Subscribe
    public void handleMessage(Integer msg) {

        if (null == mListner) {
            return;
        }
        switch (msg) {
            //进度
            case MSG_PROGRESS:
                long progress = 0;
                for (long mProgress : mProgress) {
                    progress += mProgress;
                }

                mListner.onProgress(progress * 1.0f / mFileLength);
                break;
            //暂停
            case MSG_PAUSE:
                childPauseCount++;
                if (childPauseCount % THREAD_COUNT != 0) { return; }
                resetStatus();
                mListner.onPause();
                break;
            //完成
            case MSG_FINISH:
                childFinishCount.getAndIncrement();
                if (childFinishCount.get() % THREAD_COUNT != 0) { return; }
                jobLogSender.log("所有线程完成，重命名文件");
                //下载完毕后，重命名目标文件名
                mTmpFile.renameTo(new File(mPoint.getFilePath(), mPoint.getFileName()));
                resetStatus();
                mListner.onFinished();
                jobLogSender.log("文件%s下载完成", mPoint.getFileName());
                break;
            //取消
            case MSG_CANCEL:
                childCancelCount++;
                if (childCancelCount % THREAD_COUNT != 0) { return; }
                resetStatus();
                mProgress = new long[THREAD_COUNT];
                mListner.onCancel();
                break;
            case MSF_ERROR:
                mListner.onError();
            default:
                break;
        }
    }

    public synchronized void start() {
        try {
            jobLogSender.log("开始下载: " + isDownloading + "\t" + mPoint.getUrl());
            if (isDownloading) { return; }
            isDownloading = true;
            mHttpUtil.getContentLength(mPoint.getUrl(), new okhttp3.Callback() {
                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    if (response.code() != 200) {
                        close(response.body());
                        resetStatus();
                        sendEmptyMessage(MSF_ERROR);
                        return;
                    }
                    // 获取资源大小
                    mFileLength = response.body().contentLength();
                    log.info("mFileLength is " + mFileLength);
                    jobLogSender.log("文件大小为 " + mFileLength + " bytes");
                    close(response.body());
                    // 在本地创建一个与资源同样大小的文件来占位
                    mTmpFile = new File(mPoint.getFilePath(), mPoint.getFileName() + ".tmp");
                    if (!mTmpFile.getParentFile().exists()) { mTmpFile.getParentFile().mkdirs(); }
                    RandomAccessFile tmpAccessFile = new RandomAccessFile(mTmpFile, "rw");
                    tmpAccessFile.setLength(mFileLength);
                    /*将下载任务分配给每个线程*/
                    // 计算每个线程理论上下载的数量.
                    long blockSize = mFileLength / THREAD_COUNT;

                    /*为每个线程配置并分配任务*/
                    for (int threadId = 0; threadId < THREAD_COUNT; threadId++) {
                        // 线程开始下载的位置
                        long startIndex = threadId * blockSize;
                        // 线程结束下载的位置
                        long endIndex = (threadId + 1) * blockSize - 1;
                        // 如果是最后一个线程,将剩下的文件全部交给这个线程完成
                        if (threadId == (THREAD_COUNT - 1)) {
                            endIndex = mFileLength - 1;
                        }
                        jobLogSender.log("分配线程%d,开始下载%d至%d", threadId, startIndex, endIndex);
                        // 开启线程下载
                        download(startIndex, endIndex, threadId);
                    }
                }

                @Override
                public void onFailure(Call call, IOException e) {
                }
            });
        } catch (IOException e) {
            log.error("IOException", e);
            jobLogSender.log("文件读写异常");
            resetStatus();
            sendEmptyMessage(MSF_ERROR);
        }
    }

    public void download(final long startIndex, final long endIndex, final int threadId) throws IOException {
        long newStartIndex = startIndex;
        // 分段请求网络连接,分段将文件保存到本地.
        // 加载下载位置缓存文件
        final File cacheFile = new File(mPoint.getFilePath(),
            "thread" + threadId + "_" + mPoint.getFileName() + ".cache");
        mCacheFiles[threadId] = cacheFile;
        final RandomAccessFile cacheAccessFile = new RandomAccessFile(cacheFile, "rwd");
        // 如果文件存在
        if (cacheFile.exists()) {
            String startIndexStr = cacheAccessFile.readLine();
            try {
                //重新设置下载起点
                newStartIndex = Integer.parseInt(startIndexStr);
            } catch (NumberFormatException e) {

            }
        }
        final long finalStartIndex = newStartIndex;
        mHttpUtil.downloadFileByRange(mPoint.getUrl(), finalStartIndex, endIndex, new okhttp3.Callback() {
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                // 206：请求部分资源成功码
                if (response.code() != 206) {
                    resetStatus();
                    return;
                }
                // 获取流
                InputStream is = response.body().byteStream();
                // 获取前面已创建的文件.
                RandomAccessFile tmpAccessFile = new RandomAccessFile(mTmpFile, "rw");
                // 文件写入的开始位置.
                tmpAccessFile.seek(finalStartIndex);
                /*  将网络流中的文件写入本地*/
                byte[] buffer = new byte[1024 << 2];
                int length = -1;
                // 记录本次下载文件的大小
                int total = 0;
                long progress = 0;
                while ((length = is.read(buffer)) > 0) {
                    if (cancel) {
                        //关闭资源
                        close(cacheAccessFile, is, response.body());
                        cleanFile(cacheFile);
                        sendEmptyMessage(MSG_CANCEL);
                        return;
                    }
                    if (pause) {
                        //关闭资源
                        close(cacheAccessFile, is, response.body());
                        //发送暂停消息
                        sendEmptyMessage(MSG_PAUSE);
                        return;
                    }
                    tmpAccessFile.write(buffer, 0, length);
                    total += length;
                    progress = finalStartIndex + total;

                    //将当前现在到的位置保存到文件中
                    cacheAccessFile.seek(0);
                    cacheAccessFile.write((progress + "").getBytes("UTF-8"));
                    //发送进度消息
                    mProgress[threadId] = progress - startIndex;

                    sendEmptyMessage(MSG_PROGRESS);
                }
                //关闭资源
                close(cacheAccessFile, is, response.body());
                // 删除临时文件
                jobLogSender.log("线程%d完成，删除临时文件", threadId);
                cleanFile(cacheFile);
                //发送完成消息
                sendEmptyMessage(MSG_FINISH);
            }

            @Override
            public void onFailure(Call call, IOException e) {
                isDownloading = false;
            }
        });
    }

    private void sendEmptyMessage(int msg) {
        eventBus.post(msg);
    }

    /**
     * 关闭资源
     *
     * @param closeables
     */
    private void close(Closeable... closeables) {
        int length = closeables.length;
        try {
            for (Closeable closeable : closeables) {
                if (null != closeable) { closeable.close(); }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            for (int i = 0; i < length; i++) {
                closeables[i] = null;
            }
        }
    }

    /**
     * 删除临时文件
     */
    private void cleanFile(File... files) {
        if (!(files == null || files.length == 0)) {
            Arrays.stream(files).forEach(file -> file.delete());
        }
    }

    /**
     * 暂停
     */
    public void pause() {
        pause = true;
    }

    /**
     * 取消
     */
    public void cancel() {
        cancel = true;
        cleanFile(mTmpFile);
        if (!isDownloading) {
            if (null != mListner) {
                cleanFile(mCacheFiles);
                resetStatus();
                mListner.onCancel();
            }
        }
    }

    /**
     * 重置下载状态
     */
    private void resetStatus() {
        pause = false;
        cancel = false;
        isDownloading = false;
    }

    public boolean isDownloading() {
        return isDownloading;
    }
}